/*********************************************
 * Original Author: Guido Socher
 * Original Template: server-www-simple
 * ZoneAVR Hack by snake
 * Copyright: GPL V3
 *
 *
 * http://tuxgraphics.org/electronics/
 * Chip type           : Atmega88/168/328/644 with ENC28J60
 * Use chip 328 or better. Need at least 2K sram.




"Any intelligent fool can make things bigger, more complex, and more violent. It takes a touch of genius—and a
lot of courage to move in the opposite direction." E.F.Schumacher

Welcome to ZoneAVR Camp A.



In order to set this up.


Must configure a Unique MAC address.
Must configure the IP address and Port of the ZMTrigger Daemon on the ZM Server, that you are
connecting to.
Must configure what you are sending to the ZMTrigger Daemon in the packet.
(optional) May or may not configure a schedule for when to have the ZMTrigger daemon active.
If you configure the schedule, you must specify the NTP server IP address and port.
(optional) For PIR sensors, you may want to tune the sensitivity potentiometer on them.


NOTE: if you want to use the schedule, you will have to look down in the main()
section of the code for the schedule and configure appropriately.

 *********************************************/



#include <avr/io.h>
#include <stdlib.h>
#include <string.h>
#include "../ip_arp_udp_tcp.h"
#include "../websrv_help_functions.h"
#include "../enc28j60.h"
#include "../timeout.h"

// Power saving & Sleep
// https://www.gammon.com.au/power
//Disable timer0,2
//Disable Two wire interface (i2c)
//No need to disable brown out with arduino uno board, and vreg
//Custom PCB can do further power saving

#include <avr/sleep.h>
#include <avr/power.h>

// UART
#include <stdio.h>
#include "uart.h"

#define F_CPU 16000000UL

// EEPROM
#include <avr/eeprom.h>

// DHCP
#include <avr/interrupt.h>
#include "../websrv_help_functions.h"
#include "../net.h"
#include "../dnslkup.h"
#include "../dhcp_client.h"


/* 		LOGGING			*/

// all logging should be in flash using
// PSTR or other macros PROGMEM, etc...
// Otherwise not enough RAM

// Undef to remove logging for main and startup
//#define DEBUG
#undef DEBUG

#ifdef DEBUG
 #define log
#else
 #define log for(;0;)
#endif

//Undef to remove logging on url parsing
//#define DEBUGURL
#undef DEBUGURL

#ifdef DEBUGURL
 #define urllog
#else
 #define urllog for(;0;)
#endif


//Undef to remove logging on ram
//#define DEBUGRAM
#undef DEBUGRAM

#ifdef DEBUGRAM
 #define ramlog
#else
 #define ramlog for(;0;)
#endif

//Undef to remove logging on eeprom
//#define DEBUGEEPROM
#undef DEBUGEEPROM

#ifdef DEBUGEEPROM
 #define eelog
#else
 #define eelog for(;0;)
#endif


//Undef to remove logging on general program state

//#define DEBUGGEN
#undef DEBUGGEN

#ifdef DEBUGGEN
 #define genlog
#else
 #define genlog for(;0;)
#endif


//variable for print freeram
uint16_t d;

// for loops
uint8_t x = 0;
uint8_t z = 0;
int8_t y = 0;

char* error = " All Systems Green     ";

uint8_t tmpnum;












// VARIABLES TO FILL IN

// The below variables should be customized to each ZoneAVR.




/* MICROWAVESENSOR

If you use the Microwave sensor, instead of PIR, set this to 1. Else 0.

*/

#define MICROWAVESENSOR 1   //1 on  0 off
#define PIRON 0   // 1 pir off  0 pir on, if microwave sensor is 0




/*

	ZMTrigger Command to Send

B<id>|B<action>|B<score>|B<cause>|B<text>|B<showtext>
which in this code is:
monnum | onoff + timealarm | score | source

e.g.
2|on+5|100|ZoneAVR||

This will send a command to ZMTrigger.pl to turn monitor #2 ON (alarm state) for five seconds, with a score of 100
and the source of ZoneAVR. The text field, and show text are not setup here.

*/



//monitor number
char* monnum = "25";
//command to send to zmtrigger. 
char* onoff = "on";
//time to set monitor to alarm
char* timealarm = "10";
//score to assign
char* score = "100";
//source
char* source = "ZoneAVR";

//microphone (not implemented)
uint8_t micpwr = 0;
//modect led 1 for on. 0 for off (not implemented)
uint8_t modectled = 0;


// ip of ZM server where zmtrigger.pl is listening
uint8_t destinationip[4] = {192,168,1,178};
//zmtrigger.pl port. default is 6802
uint16_t destinationport = 6802;






/*

 	NTP / Schedule

*/
//set to 1 to enable ntp time, and schedules. 0 to disable.
uint8_t ntpenableflag = 0;
//flag for running ntp once on boot. Set to 1, to wait ntptimewrap before asking ntp req.
//Normally set to 0 and not changed.
uint8_t ntponboot = 0;

//ip of ntp server
uint8_t ntpip[4] = {192,168,1,115};

#define NTPPORT 123
#define NTPSYNCINTERVAL 600 //not used
#define NTPTIMEWRAP 120 // how often to poll ntp
#define GETTIMEOFDAY_TO_NTP_OFFSET 2208988800UL

// change summer/winter time and your timezone here (GMT +1 is Germany, France etc... in winter), unit is hours times 10. The $
// compile time default and you can change it at run-time just by
// pressing the push button on the board.
//
//int8_t hours_offset_to_utc=+10;  // +10 means +1.0 hours = +1 hour

// US/Canada eastern time in summer (-4 hours):
int16_t hours_offset_to_utc=-40;


/*	Days you want ZoneAVR On or Off	*/
// not implemented yet
uint8_t  onSun = 0;
uint8_t  onMon = 0;
uint8_t  onTue = 0;
uint8_t  onWed = 0;
uint8_t  onThu = 0;
uint8_t  onFri = 0;
uint8_t  onSat = 0;
uint8_t  onWeekday = 0;
uint8_t  onWeekend = 0;
uint8_t  RegularWorkHours = 1;
char* mon = "Mon";
char* tue = "Tue";
char* wed = "Wed";
char* thu = "Thu";
char* fri = "Fri";







// listen port for www
#define MYWWWPORT 80



//ip address of ZoneAVR. Set ip to 0,0,0,0 for DHCP
uint8_t myip[4]    ={0,0,0,0};
//Gateway of Network. If using DHCP this should be 0,0,0,0.
uint8_t gwip[4]    ={0,0,0,0};
//Netmask
uint8_t netmask[4] ={255,255,255,0};
//ZoneAVR Mac address
//MAC address must be different from other items on the network
uint8_t mymac[6]   ={0x5a,0x4f,0x4e,0x45,0x00,0x3f};


//make initial 9 char buffer for password
char* password="Peach"; // the password string (only a-z,0-9,_ characters)



















/*

			Nothing below should need to be changed.
			Except for the schedule section. TODO: bring schedule
			up here.


*/


uint8_t schedule          = 1;

uint8_t eepromloadflag    = 0;

uint8_t ntpmac[6]         = {0x11,0x11,0x11,0x11,0x11,0x11};
uint8_t destinationmac[6] = {0x11,0x11,0x11,0x11,0x11,0x11};
static uint8_t dhcpip[4]  = {0,0,0,0};


char value[10];

//variable for print freeram
uint16_t d;




// NTP

#include <inttypes.h>
#include <avr/pgmspace.h>

uint32_t time =   0;

uint8_t haveNTPanswer =  0;
uint8_t ntp_retry_count =0;


// EPOCH = Jan 1 1970 00:00:00
#define EPOCH_YR        1970
//(24L * 60L * 60L)
#define SECS_DAY        86400UL
#define LEAPYEAR(year)  (!((year) % 4) && (((year) % 100) || !((year) % 400)))
#define YEARSIZE(year)  (LEAPYEAR(year) ? 366 : 365)

char day[16];
char clock[12]= "blank start";

static const char day_abbrev[] PROGMEM = "SunMonTueWedThuFriSat";


//made global, so other functions can work with
char dstr[4]; // week days
uint8_t tm_sec,tm_min,tm_hour,tm_wday,tm_mon;
//is it faster for this to be global, or to parse it again? I'd assume former
//given enough space






uint8_t monthlen(uint8_t isleapyear,uint8_t month){
        if(month==1){
                return(28+isleapyear);
        }
        if (month>6){
                month--;
        }
        if (month%2==1){
                return(30);
        }
        return(31);
}
// gmtime -- convert calendar time (sec since 1970) into broken down time
// returns something like Fri 2007-10-19 in day and 01:02:21 in clock
// The return values is the minutes as integer. This way you can update
// the entire display when the minutes have changed and otherwise just
// write current time (clock). That way an LCD display needs complete
// re-write only every minute.
uint8_t gmtime(const uint32_t time,uint8_t show24h,char *day, char *clock)
{
	//making this global
//        char dstr[4]; // week days
        char ampm[3]="pm";
        uint8_t i;
        uint32_t dayclock;
        uint16_t dayno;
        uint16_t tm_year = EPOCH_YR;
	//making these global
   //     uint8_t tm_sec,tm_min,tm_hour,tm_wday,tm_mon;

        dayclock = time % SECS_DAY;
        dayno = time / SECS_DAY;

        tm_sec = dayclock % 60UL;
        tm_min = (dayclock % 3600UL) / 60;
        tm_hour = dayclock / 3600UL;
        tm_wday = (dayno + 4) % 7;      /* day 0 was a thursday */
        while (dayno >= YEARSIZE(tm_year)) {
                dayno -= YEARSIZE(tm_year);
                tm_year++;
        }
        tm_mon = 0;
        while (dayno >= monthlen(LEAPYEAR(tm_year),tm_mon)) {
                dayno -= monthlen(LEAPYEAR(tm_year),tm_mon);
                tm_mon++;
        }
        i=0;
        while (i<3){
                dstr[i]= pgm_read_byte(&(day_abbrev[tm_wday*3 + i]));
                i++;
        }
        dstr[3]='\0';
        sprintf_P(day,PSTR("%s %u-%02u-%02u"),dstr,tm_year,tm_mon+1,dayno + 1);
        if (show24h){
                sprintf_P(clock,PSTR("%02u:%02u:%02u  "),tm_hour,tm_min,tm_sec);
        }else{
                if (tm_hour<12){
                        ampm[0]='a';;
                        ampm[1]='m';
                }else{
                        tm_hour-=12;
                }
                if (tm_hour==0) tm_hour=12;
                sprintf_P(clock,PSTR("%02u:%02u:%02u%s"),tm_hour,tm_min,tm_sec,ampm);
        }
        return(tm_min);
}
























// Adjust buffer size for bigger webpages. Atmega328p limited to 2K RAM
// Above a certain limit, web pages break. 
// must be no more than 800 about.
// unless you add SRAM externally.

#define BUFFER_SIZE 600
static uint8_t buf[BUFFER_SIZE+1];






//STATIC IP, used for obtaining mac address
static int8_t gw_arp_state=0;
static int8_t zmsrv_arp_state=0;

//ntp
static int8_t ntp_arp_state=0;

uint8_t gwmac[6] = {0x11,0x11,0x11,0x11,0x11,0x11};

static char urlvarstr[30];

//this required for arpresolver
//to differentiate between different mac requests (i.e. whose mac is whose)
#define TRANS_NUM_GWMAC 1
#define TRANS_NUM_ZMSRVMAC 2
#define TRANS_NUM_NTPMAC 3


// TIMER (also further down)
static volatile uint8_t sec=0; // counts up to 6 and goes back to zero
static volatile uint8_t gsec=0; // counts up beyond 6 sec
static volatile uint16_t rebootsec=0; // counts up to 36000 then reboot
				      // needed for enc28j60, otherwise it crashes every day or so
static volatile uint16_t ntpsec=0; // ntptimer



static uint8_t start_web_client=0;
static uint8_t web_client_attempts=0;
static int8_t dns_state=0;

uint8_t rebootflag = 0;





//This is the message sent to Zmtrigger.pl
//e.g.
//2|on+5|100|ZoneAVR||

//THIS MUST BE INITIALIZED WITH SOME VALUE
//BIG ENOUGH TO KEEP OLD VALUES AWAY
char*  zmtrigmessage = "1234567890123456789012345678901234";







// EEPROM Addresses 
// Atmega328p has 1k bytes
// Not being used, anymore.

#define EEFIRSTBOOT  6
//ZMTrigger message parameters 10-60

#define EEPASSWORD  100






//Jeelabs
//formula to watch amount of available RAM


int freeRam () {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}



/*              ADC, for RNG


*/


#define RAND_MAX 255

// Random number generators on avr
// Researching these...
// A LOT of smoke and mirrors
// In here is a simple ADC example that isn't being used
// But I like it, so I'm leaving it.


// ATmega328p data sheet: 21.9 ADC registers
void adc_init()
{
    // AREF = AVcc
    ADMUX = (1<<REFS0);

    // ADC Enable and prescaler of 128
    // 16000000/128 = 125000
    ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
}

uint16_t adc_read(uint8_t ch)
{
  // select the corresponding channel 0~7
  // ANDing with ’7′ will always keep the value
  // of ‘ch’ between 0 and 7


  //ch is passed into ADC_Read see above. You specify what channel to use when calling adc read
  ch &= 0b00000111;  // AND operation with 7


  //ADC1 == A1 on Uno

  ADMUX = (ADMUX & 0xF8)|ch; // clears the bottom 3 bits before ORing

  //ADMUX = (1<<REFS0);	 //duplicate


  // start single convertion
  // write ’1′ to ADSC
  ADCSRA |= (1<<ADSC);
  // wait for conversion to complete
  // ADSC becomes ’0′ again
  // till then, run loop continuously
  while(ADCSRA & (1<<ADSC));

  return (ADC);
}







// Callbacks for DHCP, get_mac_with_arp

//Be aware that this function, will fill in mac based on what reference number is
//This way you can keep track of what mac is what

void arpresolver_result_callback(uint8_t *ip __attribute__((unused)),uint8_t reference_number,uint8_t *mac){
        uint8_t i=0;
        if (reference_number==TRANS_NUM_GWMAC){
                // copy mac address over:
                while(i<6){gwmac[i]=mac[i];i++;}
        }
	if (reference_number==TRANS_NUM_ZMSRVMAC){
		//copy mac for zmserver
                while(i<6){destinationmac[i]=mac[i];i++;}
	}
	if (reference_number==TRANS_NUM_NTPMAC){
		//copy mac for zmserver
                while(i<6){ntpmac[i]=mac[i];i++;}
	}
}






/*		TIMER		*/
//http://www.robotshop.com/letsmakerobots/arduino-101-timers-and-interrupts
//Timer taken from existing tuxgraphics tcp/ip code.


// timer interrupt, called automatically every second
// (one second is based on calculations for interrupt)
ISR(TIMER1_COMPA_vect){
	time++;
        sec++;
        gsec++;
	rebootsec++;
	ntpsec++;
        if (sec>5){
                sec=0;
                dhcp_6sec_tick();
        }
}


// Generate an interrup about ever 1s form the 12.5MHz system clock
// Since we have that 1024 prescaler we do not really generate a second
// (1.00000256000655361677s)
// TODO: EDIT TIMER
void timer_init(void)
{
        /* write high byte first for 16 bit register access: */
        TCNT1H=0;  /* set counter to zero*/
        TCNT1L=0;
        // Mode 4 table 14-4 page 132. CTC mode and top in OCR1A
        // WGM13=0, WGM12=1, WGM11=0, WGM10=0
        TCCR1A=(0<<COM1B1)|(0<<COM1B0)|(0<<WGM11);
        TCCR1B=(1<<CS12)|(1<<CS10)|(1<<WGM12)|(0<<WGM13); // crystal clock/1024

        // At what value to cause interrupt. You can use this for calibration
        // of the clock. Theoretical value for 12.5MHz: 12207=0x2f and 0xaf
//ORIGINAL, tuxgraphics value. slightly fast
//        OCR1AH=0x2f;
//        OCR1AL=0xaf;


//16 MHz  0x3e80 = 16,000
        OCR1AH=0x3e;
        OCR1AL=0x80;


        // interrupt mask bit:
        TIMSK1 = (1 << OCIE1A);
}













/* WEBSITE STUFF */


// must be before print_weblog
uint16_t http200ok(void)
{
        return(fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nPragma: no-cache\r\n\r\n")));
}





// This function has about reached the limit for RAM and TCP buffer usage
// Beware when adding anything.

uint16_t print_status (uint8_t *buf){

                        uint16_t dat_p;
			uint8_t ipnetgwnum[4];
			uint8_t result[4];
			char ipconvert[7];



                        dat_p=http200ok();
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("<pre>Status:\n\n"));
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\n"));
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("If you change the MAC, beware that your DHCP ip may change.\n\n"));




			dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\n\nIP Address:"));
				for (x=0; x<4; x++){
					utoa(myip[x], ipconvert, 10);
					dat_p=fill_tcp_data(buf,dat_p,ipconvert);
					if (x<3){
						dat_p=fill_tcp_data_p(buf,dat_p,PSTR("."));
					}
				}


			dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nNetmask:"));
                                for (x=0; x<4; x++){
                                        utoa(netmask[x], ipconvert, 10);
                                        dat_p=fill_tcp_data(buf,dat_p,ipconvert);
                                        if (x<3){
                                                dat_p=fill_tcp_data_p(buf,dat_p,PSTR("."));
                                        }
                                }

                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nGateway IP:"));
                                for (x=0; x<4; x++){
                                        utoa(gwip[x], ipconvert, 10);
                                        dat_p=fill_tcp_data(buf,dat_p,ipconvert);
                                        if (x<3){
                                                dat_p=fill_tcp_data_p(buf,dat_p,PSTR("."));
                                        }
                                }

                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nZMServer IP:"));
                                for (x=0; x<4; x++){
                                        utoa(destinationip[x], ipconvert, 10);
                                        dat_p=fill_tcp_data(buf,dat_p,ipconvert);
                                        if (x<3){
                                                dat_p=fill_tcp_data_p(buf,dat_p,PSTR("."));
                                        }
                                }


			//destination port

			dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nPort:"));

			sprintf(ipconvert, "%d", destinationport);

			printf_P(PSTR("\nPort is %s\n"), ipconvert);
                        dat_p=fill_tcp_data(buf,dat_p,ipconvert);


		dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\n\nCamera Monitor:"));
		dat_p=fill_tcp_data(buf,dat_p,monnum);
		dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nCommand:"));
		dat_p=fill_tcp_data(buf,dat_p,onoff);
		dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nTime to Alarm:"));
		dat_p=fill_tcp_data(buf,dat_p,timealarm);
		dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nScore:"));
		dat_p=fill_tcp_data(buf,dat_p,score);
		dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nSource:"));
		dat_p=fill_tcp_data(buf,dat_p,source);



		// microphone and modect led
		// No need to read eeprom again. EEPROM is read at boot.
		// Use global variables, micpwr, and modectled.

		//dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nModectLED:"));
		//if(modectled == 1){
		//	dat_p=fill_tcp_data_p(buf,dat_p,PSTR("ON"));
		//}
		//if(modectled == 0){
		//	dat_p=fill_tcp_data_p(buf,dat_p,PSTR("OFF"));
		//}


		dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nMicrowave:"));
                if(MICROWAVESENSOR == 1){
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("ON"));
                }
                if(MICROWAVESENSOR == 0){
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("OFF"));
                }

		dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nPIR:"));
                if(PIRON == 1){
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("ON"));
                }
                if(PIRON == 0){
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("OFF"));
                }


		dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nNTP:"));
                if(ntpenableflag == 1){
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("ON"));
                }
                if(ntpenableflag == 0){
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("OFF"));
                }

		if (ntpenableflag == 1){
	                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nNTP IP:"));
                                for (x=0; x<4; x++){
                                        utoa(ntpip[x], ipconvert, 10);
                                        dat_p=fill_tcp_data(buf,dat_p,ipconvert);
                                        if (x<3){
                                                dat_p=fill_tcp_data_p(buf,dat_p,PSTR("."));
                                        }
                                }

		}





                        return (dat_p);
}















// Parsing and PWD


//Used for parsing GET requests. see gStrbuf
#define STR_BUFFER_SIZE 15
static char gStrbuf[STR_BUFFER_SIZE+1];















/* MISC */






/*

*/




void erase_eeprom (void){

	uint8_t zero=0;
	uint16_t i;

	printf_P(PSTR("Erasing EEPROM"));

	for (i = 0 ; i < 1000 ; i++) {
	//	EEPROM.write(i, 0);
		eeprom_write_byte (i, zero);
	}
	printf_P(PSTR("EEPROM values set to zero"));

}





/*
void load_eeprom_mac(void){

	eeprom_read_zmt (EEPASSWORD,password);

                puts("free ram is:");
                d=freeRam();
                printf("%d", d);
                puts("\n");

	//no room for these in part 1
        eeprom_read_ip(EEMACADDRESS,mymac,6);
        x=0;while(x<6){printf_P(PSTR("%2x:"),mymac[x]);x++;}

        eeprom_read_ip(EESERVERIP,destinationip,4);
        x=0;while(x<4){printf_P(PSTR("%d."),destinationip[x]);x++;}

        destinationport = eeprom_read_word(EEPORTNUM);
	if (destinationport == 0){
		destinationport = 6802;
	}
        printf_P(PSTR("port is %d\n"), destinationport);


}
*/








// First boot

uint8_t first_boot (void){

	uint8_t result;
	result  =  eeprom_read_byte(EEFIRSTBOOT);
	return result;

}






void first_boot_cleanup (uint8_t num){

		uint8_t v;

		if (num == 0){
			erase_eeprom();

			//rng
		        d = adc_read(0);
		        // seed rng with adc reading
		        srand(d);


			//randomize mac in eeprom 
			//not currently used
			//example of adc and rng, so
			//leaving in
/*
			for (x=0; x<1; x++){
				v=EEMACADDRESS;
				//write rand numbers to mac
				d = rand();
				printf_P(PSTR("%d writing to five\n"), d);
		                eeprom_write_byte (v+4, d);
				d = rand();
				printf_P(PSTR("%d writing to six\n"), d);
		                eeprom_write_byte (v+5, d);
			}

*/
		}

}











/* 		TCP Callbacks


*/


uint8_t fd;


uint16_t tcp_datafill_callback(uint8_t fd){
        fd=0; // supress warning about unused parameter

	printf_P(PSTR("Z\n"));
	printf_P(PSTR("o\n"));
	printf_P(PSTR("n\n"));
	printf_P(PSTR("e\n"));
	printf_P(PSTR("A\n"));
	printf_P(PSTR("V\n"));
	printf_P(PSTR("R\n"));



//this works
//        return(fill_tcp_data_p(buf,0,PSTR("4|on+5|Zoneavr|100||")));



// monitor|on/off/cancel/show  +time|score|source|text|texttoshow or something
// see /usr/bin/zmtrigger.pl

//be sure to initialize zmtrigmessage with something when using this.
//otherwise it will fail
//ALSO, always ALWAYS initialize any char arrays, or you will have
// overlapping data.
//there is an issue with floats and snprintf with avr. beware. search online.

//beware that the buffer in snprintf is big enough to hold everything
	snprintf(zmtrigmessage, 56, "%s|%s+%s|%s|%s||\n", monnum, onoff, timealarm, score, source);
	printf_P(PSTR("%s is the alert sent\n"), zmtrigmessage);



//	this works. needs enough memory(sram)
        return(fill_tcp_data(buf,0,zmtrigmessage));

}




// todo, implement below error checking

uint8_t tcp_result_callback(uint8_t fd, uint8_t statuscode,uint16_t datapos, uint16_t len_of_data){
//        have_modbus_answer=0;
        fd=0; // supress warning about unused parameter
  //      len_of_data=sizeof(modbus_dat);
/*        if (statuscode==0){
                modbus_client_sendok++;
                LEDOFF;
                // limit to size of available storage:
                if (len_of_data>sizeof(modbus_dat)){
                        len_of_data=sizeof(modbus_dat);
                }
                while(len_of_data){
                        modbus_dat[have_modbus_answer]=buf[datapos+have_modbus_$
                        have_modbus_answer++;
                        len_of_data--;
                }
        }*/
	printf_P(PSTR("incallback\n"));
        return(0); // end the tcp dialog from the client side
}











































/* MAIN */

int main(void){






	//prepare two pins
	// must change reset pin back to normal if used previously
	// must ready motion sensor input pin

	//if reset done during last run:
	//initialize reset pulling pin high after reset
	DDRD  = DDRD   & ~( 1 << PD2);  //PD2 input
        PORTD = PORTD  |  ( 1 << PD2);  //PD2 goes high

	//Sensor (e.g. PIR, microwave, lidar) input on PD3
	//this is uno digital pin 3
	//used because it is interrupt pin (int1)
	//as well as digital
	//todo: implement sleep and interrupts
        DDRD  = DDRD   & ~( 1 << PD3);  //PD3 input
        PORTD = PORTD  & ~( 1 << PD3);  //PD3 goes low

	uint16_t d;


	adc_init();
	uart_init();
    	stdout = &uart_output;
    	stdin  = &uart_input;

	//power saving
	power_twi_disable();   // i2c
	power_timer0_disable();// Timer 0
	power_timer2_disable();// Timer 2

	//wdt_disable();         // watchdog 


/*
interrupts not implemented
	//interrupts
	EIMSK = 1<<INT1;                                        // Enable INT1
        //EICRA |= 1<<ISC01;    // Trigger INT1 on rising edge
        EICRA |= 1<<ISC00;    // Trigger INT1 on rising edge
*/



        printf_P(PSTR("ZoneAVR Start\n"));
	uint8_t rval;
        uint16_t dat_p, plen;




        // Set the clock speed to "no pre-scaler" (8MHz with internal osc or 
        // full external speed)
        // set the clock prescaler. First write CLKPCE to enable setting 
        // of clock the next four instructions.
        // Note that the CKDIV8 Fuse determines the initial
        // value of the CKKPS bits.
        CLKPR=(1<<CLKPCE);
        CLKPR=0; // 8 MHZ
        _delay_loop_1(0); // 60us

        /*initialize enc28j60*/
        enc28j60Init(mymac);
        _delay_loop_1(0); // 60us



	timer_init();
	//enable interrupts
	sei();







        // Magjack leds configuration, see enc28j60 datasheet, page 11 
        // LEDB=yellow LEDA=green
        //
        // 0x476 is PHLCON LEDA=links status, LEDB=receive/transmit
        // enc28j60PhyWrite(PHLCON,0b0000 0100 0111 01 10);

	// the LEDs can be changed if desired
        enc28j60PhyWrite(PHLCON,0x476);







	printf_P(PSTR("Start Mac"));

	// DHCP handling. Get the initial IP
	rval=0;
	init_mac(mymac);

	//memcmp string h
	//if myip equal to 0,0,0,0 do DHCP
	if (memcmp(myip, dhcpip, sizeof(myip))==0){
			//log logthis("Static IP not set. Starting DHCP");
	        while(rval==0){
        	        plen=enc28j60PacketReceive(BUFFER_SIZE, buf);
	               	buf[BUFFER_SIZE]='\0';
	                rval=packetloop_dhcp_initial_ip_assignment(buf,plen,mymac[5]);
	        }
	        // we have an IP:
	        dhcp_get_my_ip(myip,netmask,gwip);
			int i;
	//		log puts("DHCP IP is");
	//		log for(i = 0; i < 4; i++) {
	//		    {
	//		        log printf("%d", myip[i]);
	//		    }
	//		    log printf(".");
	//		}
	        client_ifconfig(myip,netmask);

	        if (gwip[0]==0){
	                // we must have a gateway returned from the dhcp server
	                // otherwise this code will not work
	//		puts("No gateway received from DHCP server");
	                while(1); // stop here
			//todo: have LED blinking if here
	        }

	        // we have a gateway.
	        // find the mac address of the gateway (e.g your dsl router).


		//Mac request used if DHCP. Separate Mac request for static ips further below.
	        get_mac_with_arp(gwip,TRANS_NUM_GWMAC,&arpresolver_result_callback);
	        while(get_mac_with_arp_wait()){
	                // to process the ARP reply we must call the packetloop
	                plen=enc28j60PacketReceive(BUFFER_SIZE, buf);
	                packetloop_arp_icmp_tcp(buf,plen);
			//gw arp state set to two, to avoid static conflict
			gw_arp_state=2;
	        }


		urllog printf_P(PSTR("End DHCP"));
		genlog printf_P(PSTR("DHCP Ended\n"));
	}












	//Static
	//If static, it will init, with mymac, myip below


        //init the ethernet/ip layer:


        init_udp_or_www_server(mymac,myip);


        www_server_port(MYWWWPORT);

	//log logthis("Entering Main Loop");
	genlog printf_P(PSTR("Entering main while loop\n"));

        while(1){



		//plen will be greater than 0 if packet received. 0 if no packet.

		plen=enc28j60PacketReceive(BUFFER_SIZE, buf);
                buf[BUFFER_SIZE]='\0'; // http is an ascii protocol. Make sure we have a string terminator.
                // DHCP renew IP:
                plen=packetloop_dhcp_renewhandler(buf,plen); // for this to work you have to call dhcp_6sec_tick() $
                dat_p=packetloop_arp_icmp_tcp(buf,plen);


		/*		//ntp debugging
		printf_P(PSTR("time inc: %lu\n"), time);
		printf_P(PSTR("ntp inc: %u\n"), ntpsec);
		//must be %s for clock and day
		printf_P(PSTR("clocks: %s\n"), clock);
		printf_P(PSTR("days: %s\n"), day);
		*/


                // dat_p will be unequal to zero if there is a valid http get 
		// dat_p will be zero if no one is visiting server
		// dat_p will be zero, and all motion detection will take place


		// ALL IDLE COMMANDS GO UNDER DAT_P == 0
		// IE, anything you want to do goes here.
		// If a valid request comes in, then this code is skipped.
		// this means the priority is on serving first, then when no one
		// is requesting from zoneavr, we do other stuff, like detect motion




                if(dat_p==0){

			//these print constantly
	 		//printf_P(PSTR("dat p == 0\n"));


			if (plen>0){
                                // possibly a udp message
                                goto UDP;
                        }


                        // we are idle here trigger arp other stuff here




			// static ip hack. This needed if static.
			// make optional if static used?
			// may make more difficult, with more interlocking parts
                        if (gw_arp_state==0){
	//				log logthis("Static IP Detected");

                                // find the mac address of the gateway (e.g your dsl router).
                                get_mac_with_arp(gwip,TRANS_NUM_GWMAC,&arpresolver_result_callback);
                                gw_arp_state=1;
                        }
                        if (get_mac_with_arp_wait()==0 && gw_arp_state==1){
                                // done we have the mac address of the GW
                                gw_arp_state=2;
                        }




			// Get mac address of destination IP.
			// If mac can't be found, will be hangup


			//Mac address is set in arpresolver function
			//the trans num is the reference number that specifies which mac to write
			if (zmsrv_arp_state==0){
				urllog printf_P(PSTR("entering mac\n"));
				urllog printf_P(PSTR("dstip is %d,\n"), destinationip[3]);
				if(destinationip[0] != 0){
					get_mac_with_arp(destinationip,TRANS_NUM_ZMSRVMAC, &arpresolver_result_callback);
		                	zmsrv_arp_state=1;
					z = 0;
					while(x = get_mac_with_arp_wait() && z < 5){
						urllog printf_P(PSTR("sending packet.\n"));

						// to process the ARP reply we must call the packetloop
		                	        plen=enc28j60PacketReceive(BUFFER_SIZE, buf);
        			                packetloop_arp_icmp_tcp(buf,plen);

						//try five times. 
						// if no mac by then, give up. requires manual setting
						// of dst ip. 
						z++;
/*
*/
					}
					urllog printf_P(PSTR("%d is get mac with arp\n"), x);
					if (get_mac_with_arp_wait()==0 && zmsrv_arp_state==1){
	                	                // done we have the mac address of the GW
        	                	        zmsrv_arp_state=2;
						urllog printf_P(PSTR("Mac Obtained for Destip\n"));
		                        }
				// todo: put some error here if mac not obtained
				// blinking led
//					error="zm srvip bad. change ip\n";

				}
				else{

					zmsrv_arp_state=2;

				}



			}

//not working. need to fix
/*
			if (destinationmac[0] == 0x11 && destinationmac[1] == 0x11 && destinationmac[2] == 0x11 &&destinationmac[3] == 0x11 && destinationmac[4] == 0x11 && destinationmac[5] == 0x11) {
				error="ZM Server IP mac address obtain failed. Bad IP?";
			}
*/



			// get mac address for ntp server, if ntp enabled

			if (ntpenableflag == 1 && ntp_arp_state==0){
                                urllog printf_P(PSTR("entering mac\n"));
                                urllog printf_P(PSTR("ntpip is %d,\n"), ntpip[3]);
                                if(ntpip[0] != 0){
                                        get_mac_with_arp(ntpip,TRANS_NUM_NTPMAC, &arpresolver_result_callback);
                                        ntp_arp_state=1;
                                        z = 0;
                                        while(x = get_mac_with_arp_wait() && z < 5){
                                                urllog printf_P(PSTR("sending packet.\n"));

                                                // to process the ARP reply we must call the packetloop
                                                plen=enc28j60PacketReceive(BUFFER_SIZE, buf);
                                                packetloop_arp_icmp_tcp(buf,plen);

                                                //try five times.
                                                // if no mac by then, give up. requires manual setting
                                                // of dst ip. give error message on homepage
                                                z++;

                                        }
                                        urllog printf_P(PSTR("%d is get mac with arp\n"), x);
                                        if (get_mac_with_arp_wait()==0 && ntp_arp_state==1){
                                                // done we have the mac address of the GW
                                                ntp_arp_state=2;
                                                urllog printf_P(PSTR("Mac Obtained for Destip\n"));
                                        }
                                // todo: put some error here if mac not obtained
				// error="zm srvip bad. change ip\n";

                                }
                                else{

                                        ntp_arp_state=2;

                                }


/*                              if (ntpmac[0] == 0x11 && ntpmac[1] == 0x11 && ntpmac[2] == 0x11 &&  ntpmac[3] == 0x11 &&  ntpmac[4] == 0x11 &&  ntpmac[5] == 0x11 ){
                                        error="NTP IP mac address obtain failed. Bad IP?";
                                }
*/

                        }



			//check pir sensor for high value
			//if high, send packet to zm server
			// wait another three seconds before sending another packet
			// if 10 seconds pass after motion detected, send off command to zm server (IS THIS NEEDED OR NOT, NEED TO TEST)
			// edit: I tested, and no off command is required. ZM server will auto turn off
			// alarm after time runs out.






			//if schedule on



			//work hours, 9-6, no motion


			// SCHEDULE
			// Here I need to implement some checking of the schedule
			// until then, just put in the hours you want it off.
			if (ntpenableflag == 1 && tm_hour > 8 && tm_hour < 18 && strcmp(dstr, mon) == 0 || strcmp(dstr, tue) == 0 || strcmp(dstr, wed) == 0 || strcmp(dstr, thu) == 0 || strcmp(dstr, fri) == 0   ){
				schedule = 0;
				//printf_P(PSTR("\nSchedule off."));
			}
			else{
				schedule = 1;
			}





			if ((ntpenableflag == 1 && schedule == 1) || ntpenableflag == 0){

				//if NOT (time > 8 && < 18) && day == mon,tues,weds,thus,fri then motion detection
				if (MICROWAVESENSOR == 0 && PIRON == 1){ //Using PIR

					//if pin d3 high, and not less than 3 seconds since last motion detected
					if ( PIND & (1<<PD3) && gsec > 3) {
						printf_P(PSTR("Motion detected\n"));
						/*
						printf_P(PSTR("%s"), dstr);
						printf_P(PSTR("\n"));
						printf_P(PSTR("%u"), tm_hour);
						printf_P(PSTR("\n"));
						*/

						client_tcp_req(tcp_result_callback,tcp_datafill_callback,destinationport,destinationip,destinationmac);
						printf_P(PSTR("finished sending packet\n"));
						gsec=0;	//start timer again
					}
				}
			}




			//Microwave Sensor ADC Read.
			//I'm not using schedule for microwave
			if (MICROWAVESENSOR == 1){
			        d = adc_read(1);
				_delay_loop_1(0); //60us
				printf_P(PSTR("%d"),d);
				printf_P(PSTR("\nMicrowave Value\n"));
				if (d > 563 && gsec > 3 || d < 543 && gsec > 3) {
                                        printf_P(PSTR("MWaves detected. Value:\n"));
					printf_P((int)d);
					printf_P(PSTR("\n"));
                                        client_tcp_req(tcp_result_callback,tcp_datafill_callback,destinationport,destinationip,destinationmac);
                                        printf_P(PSTR("finished sending packet\n"));
                                        gsec=0;//start timer again
				}
			}


			// never want timer to wrap, and miss 3 seconds of motion
			if (gsec > 240){
				gsec = 5;
			}



			//NTP SYNC


			// if ntp enabled, and less than 5 seconds since wrap
			// also, ntponboot once

			if ((ntpenableflag == 1 && ntpsec < 1) || (ntponboot == 0 && ntpenableflag == 1)){
				printf_P(PSTR("ntp request start\n"));

				client_ntp_request(buf, ntpip, NTPPORT, ntpmac);

				haveNTPanswer=0;
				if (ntponboot == 0){
					ntponboot = 1;
				}
				//goto UDP;
			}

			// every time it wraps, start it over
			if (ntpsec > NTPTIMEWRAP){
				ntpsec = 0;
			}







			//ethernet adaptor requires reset every so often
			//reboot set to every ten hours
			if (rebootsec > 36000){
				rebootflag=1;
				goto REBOOT;
			}





			//then after, send tcp packet, if high, with value ON, for camera
                        continue;
                }




                // tcp port 80 begin
                if (strncmp("GET ",(char *)&(buf[dat_p]),4)!=0){
                        // head, post and other methods:
                        //
                        // for possible status codes see:
                        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
                        dat_p=http200ok();
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("<h1>200 OK</h1>"));
//			genlog printf_P(PSTR("GET request only received.\n"));
//			printf_P(PSTR("dat p is ,\n   %s \n"),dat_p);
//			printf_P(PSTR("buf is ,\n   %s \n"),buf);

                        goto SENDTCP;
                }


		/* BEWARE there is a packet limit with this library as it sends all in one packet */
		/* Pages must be a bit under 1200 bytes */

		/* <pre> and \n and cleanest html, cleaner than <br> <p> */



		//home homepage

                if (strncmp("/ ",(char *)&(buf[dat_p+4]),2)==0){
//			logthis("root request");

                        dat_p=http200ok();
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("<pre>"));
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("<a href=\"./status\">Status Info</a>\n"));
			// this message will be the debug log of sorts.
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\n"));
                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("System Msg: "));
                        dat_p=fill_tcp_data(buf,dat_p,error);
			if (ntpenableflag == 1){
	                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nTime: "));
        	                dat_p=fill_tcp_data(buf,dat_p,clock);
	                        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\nDay: "));
        	                dat_p=fill_tcp_data(buf,dat_p,day);
			}

                        goto SENDTCP;
                }







//for testing
/*

		if (strncmp("/firstboot",(char *)&(buf[dat_p+4]),9)==0){
                        //logthis("test received");

        	        tmpnum = EEFIRSTBOOT;
                	x = 0;
	                eeprom_write_byte (tmpnum, x);
                	printf_P(PSTR("First boot set back to 0"));
			rebootflag=1;

                        goto SENDTCP;
                }
*/



		if (strncmp("/status",(char *)&(buf[dat_p+4]),6)==0){
                        //logthis("status request");

			dat_p=print_status(buf);

                        goto SENDTCP;
                }












UNAUTH:
                // all other URLs:
                dat_p=fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 401 Unauthorized\r\nContent-Type: text/html\r\n\r\n<h1>401 Unauthorized</h1>"));


SENDTCP:
	        dat_p=fill_tcp_data_p(buf,dat_p,PSTR("\n\n<hr><a href=\".\">ZoneAVR Root</a>\n</pre>"));
                // server respond with web page
		www_server_reply(buf,dat_p);

UDP:
		//taken from ntpclock per tuxgraphics
		// UDP/NTP protocol handling and idel tasks
                // check if ip packets are for us:
//                if(eth_type_is_ip_and_my_ip(buf,plen)){



//			printf_P(PSTR("*buf is ,\n   %s \n"),buf);

//			printf_P(PSTR("Processing UDP\n"));
			//was getting errors, so I eliminated one check in this
			//eth type is ip and my ip
			if(eth_type_is_ip_and_my_ip(buf,plen)){
			printf_P(PSTR("UDP received"));
			printf_P(PSTR("\n"));

				if (ntpenableflag ==1){
				//
				// 0 for any port on third parameter
        	                y = client_ntp_process_answer(buf,&time,0);
                                // convert to unix time:
				//required for gmtime conversions


					if (y == 1){




	                	                if ((time & 0x80000000UL) ==0){
        		                                // 7-Feb-2036 @ 06:28:16 UTC it will wrap:
        	        	                        time+=2085978496;
	                        	        }else{



					        	// from now until 2036:
							//get time of day offset is subjracted from time
							//otherwise you get times 50 years off
        	        	                        time-=GETTIMEOFDAY_TO_NTP_OFFSET;
                		                }
					}
					else{
						printf_P(PSTR("No ntp time received."));
						error="Failed to receive NTP time";
					}

					//gmtime seems to be corrupting memory somehow

					//workaround was to have ntp sync itself but not update gmtime. it only syncs based on ntp
					//sync defines at the top of document
					//otherwise time does not change.


					// the 0 is for 24hour clock display or not
					// use 1 for 24 hours
					gmtime(time+(int32_t)+360*(int32_t)hours_offset_to_utc,1,day,clock);

				} //end if ntpenabled
			} //end udp receive




REBOOT:
		//reboot if rebootflag checked
		//reboot only after sending HTTP response
	        if (rebootflag==1){
                	DDRD = DDRD | ( 1 << PD2);   // PD2 Output
                                                     // set to 1 for output
	                PORTD = PORTD & ~(1 << PD2); // PD2 goes low
        	}


        }



        return (0);
}
